package org.zpengblog.cosdemo.utils;

import com.qcloud.Module.Sts;
import com.qcloud.QcloudApiModuleCenter;
import com.qcloud.Utilities.Json.JSONObject;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.BasicSessionCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.auth.COSSigner;
import com.qcloud.cos.exception.CosClientException;
import com.qcloud.cos.exception.CosServiceException;
import com.qcloud.cos.http.HttpMethodName;
import com.qcloud.cos.model.COSObject;
import com.qcloud.cos.model.GeneratePresignedUrlRequest;
import com.qcloud.cos.model.GetObjectRequest;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.region.Region;
import com.tencent.cloud.CosStsClient;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

@Component
public class CosUtil {

    private Logger logger = LoggerFactory.getLogger(CosUtil.class);

    @Autowired
    private RedisUtil redis;

    private static final String COS_CREDENTIAL_KEY = "cos_credential";

    //过期时间
    private static final int DURATION = 7200;

    @Autowired
    private CosConfig cos;

    // 获取配置文件开发环境
    @Value("${spring.profiles.active}")
    private String environment;

    /**
     * cos访问前缀
     */
    public String getCosPrefix() {
        return "https://" + cos.getBucket() + "-" + cos.getAppId() + ".cos." + cos.getRegion() + ".myqcloud.com/";
    }

    /**
     * 获取文件归档key
     *
     * @return environment/yyyy/MM/dd
     */
    public String getCosKey() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("/yyyy/MM/dd/");
        String ymd = simpleDateFormat.format(new Date());
        System.out.println(environment+ymd);
        return environment + ymd;

    }

    /**
     * 获取临时访问权限
     */
    public String findSignedUrlByObjectKey(String objectKey) throws Exception {

        if (StringUtils.isBlank(objectKey)) {
            return null;
        }
        long expireSeconds = 7 * 24 * 60 * 60;
        Date expireDate = new Date(System.currentTimeMillis() + expireSeconds
                * 1000);

        COSClient cosClient = getCOSClient();
        String bucketName = cos.getBucket() + "-" + cos.getAppId();
        GeneratePresignedUrlRequest req =
                new GeneratePresignedUrlRequest(bucketName, objectKey, HttpMethodName.GET);
        req.setExpiration(expireDate);
        URL url = cosClient.generatePresignedUrl(req);
        cosClient.shutdown();

        return url == null ? null : url.toString();
    }

    public FederationToken getFederationToken() {
        try {
            TreeMap<String, Object> config = new TreeMap<String, Object>();
            config.put("SecretId", cos.getSecretId());
            config.put("SecretKey", cos.getSecretKey());
            config.put("RequestMethod", "GET");
            config.put("DefaultRegion", "gz");// 区域参数，可选: gz: 广州; sh: 上海; hk: 香港; ca: 北美;

            QcloudApiModuleCenter module = new QcloudApiModuleCenter(new Sts(), config);
            TreeMap<String, Object> params = new TreeMap<String, Object>();
            params.put("name", cos.getBucket());
            String policy = "{\"statement\": [{\"action\": [\"name/cos:GetObject\",\"name/cos:PutObject\"],\"effect\": \"allow\",\"resource\":[\"qcs::cos:" + cos.getRegion() + ":uid/" + cos.getAppId() + ":prefix//" + cos.getAppId() + "/" + cos.getBucket() + "/*\"]}],\"version\": \"2.0\"}";
            params.put("policy", policy);

            JSONObject result = new JSONObject(module.call("GetFederationToken", params));
            int code = result.getInt("code");
            String codeDesc = result.getString("codeDesc");
            String message = result.getString("message");
            Assert.isTrue(code == 0 && codeDesc.equals("Success"), message);

            JSONObject data = result.getJSONObject("data");
            Date expiredTime = new Date(data.getLong("expiredTime") * 1000);
            JSONObject credentials = data.getJSONObject("credentials");

            FederationToken token = new FederationToken();
            token.TmpSecretId = credentials.getString("tmpSecretId");
            token.TmpSecretKey = credentials.getString("tmpSecretKey");
            token.SessionToken = credentials.getString("sessionToken");
            token.ExpiredTime = expiredTime;
            return token;
        } catch (Exception e) {
            logger.info("cos 临时密钥获取失败 【{}】 ", e.getMessage());
            throw new RuntimeException("cos 临时密钥获取失败");
        }
    }

    /**
     * 前端上传的临时密钥
     */
    public Map<String, Object> getCredential() {
        Map<String, Object> map = new HashMap<>();
        String credentialStr = redis.get(COS_CREDENTIAL_KEY);
        if (StringUtils.isBlank(credentialStr)) {
            try {
                TreeMap<String, Object> config = new TreeMap<String, Object>();
                config.put("SecretId", cos.getSecretId());
                config.put("SecretKey", cos.getSecretKey());
                // 临时密钥有效时长，单位是秒，默认1800秒，最长可设定有效期为7200秒
                config.put("durationSeconds", DURATION);
                config.put("bucket", cos.getBucket() + "-" + cos.getAppId());
                config.put("region", cos.getRegion());
                // 这里改成允许的路径前缀，可以根据自己网站的用户登录态判断允许上传的具体路径，例子：a.jpg 或者 a/* 或者 * 。
                // 如果填写了“*”，将允许用户访问所有资源；除非业务需要，否则请按照最小权限原则授予用户相应的访问权限范围。
                config.put("allowPrefix", "*");

                // 密钥的权限列表。简单上传、表单上传和分片上传需要以下的权限，其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
                String[] allowActions = new String[]{
                        // 简单上传
                        "name/cos:OptionsObject",
                        "name/cos:PutObject",
                        "name/cos:GetObject",
                        "name/cos:DeleteObject",
                        // 表单上传、小程序上传
                        "name/cos:PostObject",
                        // 分片上传
                        "name/cos:InitiateMultipartUpload",
                        "name/cos:ListMultipartUploads",
                        "name/cos:ListParts",
                        "name/cos:UploadPart",
                        "name/cos:CompleteMultipartUpload"
                };
                config.put("allowActions", allowActions);

                map = CosStsClient.getCredential(config).toMap();
                //成功返回临时密钥信息，如下打印密钥信息
                map.put("keyPrefix", getCosKey());
                map.put("bucket", cos.getBucket() + "-" + cos.getAppId());
                map.put("region", cos.getRegion());

                credentialStr = com.alibaba.fastjson.JSONObject.toJSONString(map);
                redis.setEx(COS_CREDENTIAL_KEY, credentialStr, DURATION, TimeUnit.SECONDS);
//                redis.expire(COS_CREDENTIAL_KEY, Long.valueOf(DURATION), TimeUnit.SECONDS);
            } catch (Exception e) {
                //失败抛出异常
                logger.info("cos 获取零时密钥失败 【{}】", e.getMessage());
                throw new RuntimeException("cos 临时密钥获取失败");
            }
        }
        map = com.alibaba.fastjson.JSONObject.parseObject(credentialStr, Map.class);

        return map;
    }

    /**
     * 上传文件
     *
     * @param fileInputStream 文件流
     * @param key             对象在存储桶中的唯一标识
     *                        若 COS 上已存在同样 Key 的对象，上传时则会覆盖旧的对象。
     * @return
     * @throws Exception
     */
    public PutObjectResult fileUpload(InputStream fileInputStream, String fileName, String key) throws Exception {

        key = key + fileName;

        COSClient cosClient = getCOSClient();
        // 设置ObjectMetadata
        ObjectMetadata objectMetadata = new ObjectMetadata();
        // 设置输入流长度
        objectMetadata.setContentLength(fileInputStream.available());

        // 设置 Content type, 默认是 application/octet-stream

        //申迪环境
        FileTypeMap fileTypeMap = new MimetypesFileTypeMap();
        String contentType = fileTypeMap.getContentType(fileName);

        //腾云环境
        /*String contentType = URLConnection.guessContentTypeFromName(fileName);*/

        objectMetadata.setContentType(contentType);

        // 上传 object, 建议 20M 以下的文件使用该接口
//        String bucketName = cos.getBucket() + "-" + cos.getAppId();
        String bucketName = cos.getBucket() + "-" + cos.getAppId();
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, fileInputStream, objectMetadata);
        PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest);
        Date expiration = new Date(new Date().getTime() + 5 * 60 * 10000);
        URL url = cosClient.generatePresignedUrl(bucketName, key, expiration);
        String s = url.toString();
        String realUrl = s.substring(0, s.indexOf("?"));
        logger.error("返回地址【{}】", realUrl);
        cosClient.shutdown();
        return putObjectResult;
    }

    /**
     * 文件下载
     *
     * @param key
     * @return
     * @throws Exception
     */
    public InputStream download(String key) throws Exception {
        COSClient cosClient = getCOSClient();
        String bucketName = cos.getBucket() + "-" + cos.getAppId();
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, key);
        COSObject object = cosClient.getObject(getObjectRequest);
        return object.getObjectContent();
    }

    /**
     * 删除文件
     *
     * @param key
     * @throws Exception
     */
    public void delete(String key) throws Exception {
        COSClient cosClient = getCOSClient();
        String bucketName = cos.getBucket() + "-" + cos.getAppId();
        cosClient.deleteObject(bucketName, key);
        cosClient.shutdown();
    }

    /**
     * 获取cosClient.
     *
     * @return
     * @throws Exception
     */
    public COSClient getCOSClient() throws Exception {
        //获取临时token
        FederationToken token = getFederationToken();
        BasicSessionCredentials cred = new BasicSessionCredentials(token.TmpSecretId, token.TmpSecretKey, token.SessionToken);
        // 2 设置 bucket 的区域, COS 地域的简称请参阅 https://cloud.tencent.com/document/product/436/6224
        // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参阅源码或者常见问题 Java SDK 部分
        Region region = new Region(cos.getRegion());
        ClientConfig clientConfig = new ClientConfig(region);
        // 3 生成 cos 客户端
        COSClient cosClient = new COSClient(cred, clientConfig);
        return cosClient;
    }


    /**
     * 对url签名
     *
     * @param resPath 文件路径
     * @param key
     * @throws Exception
     */
    public String urlSign(String resPath, String key) throws Exception {
        String secretId = cos.getSecretId();
        String secretKey = cos.getSecretKey();
        COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
        COSSigner signer = new COSSigner();
        // 设置过期时间
        Date expiredTime = new Date(System.currentTimeMillis() + 3600L * 1000L * 24 * 365 * 10);
        // 要签名的 key, 生成的签名只能用于对应此 key 的下载
        //String key = "/dev/2020/07/11/1594461718196/test.png";
        //String resPath = "https://touri-industry-manage-1254950508.cos.ap-guangzhou.myqcloud.com/dev/2020/07/11/1594461718196/test.png";
        //q-sign-algorithm=sha1&q-ak=AKIDrTjjqj0dfPsK8jDqLrLax3hFdroc7Oaa&q-sign-time=1594465523;1594469123&q-key-time=1594465523;1594469123&q-header-list=&q-url-param-list=&q-signature=031659641f4ad08087aadde32c6db5948a648b0c
        String sign = signer.buildAuthorizationStr(HttpMethodName.GET, key, cred, expiredTime);
        return resPath + "?" + sign;
    }


    /**
     * 私有写
     *
     * @param fileInputStream
     * @param key
     * @throws Exception
     */
    public void privatedUpload(InputStream fileInputStream, String key) throws Exception {
        // 用户基本信息
        Map<String, Object> credential = getCredential(); //获取临时秘钥
        Map<String, Object> credentials = (Map<String, Object>) credential.get("credentials");
        String tmpSecretId = credentials.get("tmpSecretId").toString();   // 替换为您的 获取临时秘钥后的tmpSecretId
        String tmpSecretKey = credentials.get("tmpSecretKey").toString();  // 替换为您的 tmpSecretKey
        String sessionToken = credentials.get("sessionToken").toString();  // 替换为您的 sessionToken
        // 1 初始化用户身份信息(secretId, secretKey)
        COSCredentials cred = new BasicCOSCredentials(tmpSecretId, tmpSecretKey);
        // 2 设置 bucket 区域,详情请参阅 COS 地域 https://cloud.tencent.com/document/product/436/6224
        ClientConfig clientConfig = new ClientConfig(new Region(cos.getRegion()));
        // 3 生成 cos 客户端
        COSClient cosclient = new COSClient(cred, clientConfig);
        // bucket 名需包含 appid
        String bucketName = cos.getBucket() + "-" + cos.getAppId();
        // 上传 object, 建议 20M 以下的文件使用该接口
        // 设置 x-cos-security-token header 字段
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setSecurityToken(sessionToken);
        PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, key, fileInputStream, objectMetadata);
        putObjectRequest.setMetadata(objectMetadata);
        try {
            PutObjectResult putObjectResult = cosclient.putObject(putObjectRequest);
            // 成功：putobjectResult 会返回文件的 etag
            String etag = putObjectResult.getETag();
            logger.info("地址 {}", etag);
        } catch (CosServiceException e) {
            //失败，抛出 CosServiceException
            e.printStackTrace();
        } catch (CosClientException e) {
            //失败，抛出 CosClientException
            e.printStackTrace();
        }
        // 关闭客户端
        cosclient.shutdown();
    }

    /**
     * 私有读
     *
     * @param key
     * @throws Exception
     */
    public String privatedSign(String key) {
        // 传入获取到的临时密钥 (tmpSecretId, tmpSecretKey, sessionToken)
        Map<String, Object> credential = getCredential(); //获取临时秘钥
        Map<String, Object> credentials = (Map<String, Object>) credential.get("credentials");
        String tmpSecretId = credentials.get("tmpSecretId").toString();   // 替换为您的 获取临时秘钥后的tmpSecretId
        String tmpSecretKey = credentials.get("tmpSecretKey").toString();  // 替换为您的 tmpSecretKey
        String sessionToken = credentials.get("sessionToken").toString();  // 替换为您的 sessionToken
        COSCredentials cred = new BasicSessionCredentials(tmpSecretId, tmpSecretKey, sessionToken);
        // 设置 bucket 的区域, COS 地域的简称请参照 https://cloud.tencent.com/document/product/436/6224
        // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分
        Region region = new Region(cos.getRegion());
        ClientConfig clientConfig = new ClientConfig(region);
        // 生成 cos 客户端
        COSClient cosClient = new COSClient(cred, clientConfig);
        // 存储桶的命名格式为 BucketName-APPID
        String bucketName = cos.getBucket() + "-" + cos.getAppId();
        //String key = "dev/2020/07/291596016715726test001.jpg";
        GeneratePresignedUrlRequest req =
                new GeneratePresignedUrlRequest(bucketName, key, HttpMethodName.GET);
        // 设置下载时返回的 http 头
        /*ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides();
        String responseContentType = "image/x-icon";
        String responseContentLanguage = "zh-CN";
        String responseContentDispositon = "filename=\"test001.jpg\"";
        String responseCacheControl = "no-cache";
        String cacheExpireStr =
                DateUtils.formatRFC822Date(new Date(System.currentTimeMillis() + 24L * 3600L * 1000L));
        responseHeaders.setContentType(responseContentType);
        responseHeaders.setContentLanguage(responseContentLanguage);
        responseHeaders.setContentDisposition(responseContentDispositon);
        responseHeaders.setCacheControl(responseCacheControl);
        responseHeaders.setExpires(cacheExpireStr);
        req.setResponseHeaders(responseHeaders);*/
        // 设置签名过期时间(可选)，若未进行设置，则默认使用 ClientConfig 中的签名过期时间(1小时)
        // 这里设置签名在半个小时后过期
        Date expirationDate = new Date(System.currentTimeMillis() + 30L * 60L * 1000L);
        req.setExpiration(expirationDate);
        URL url = cosClient.generatePresignedUrl(req);
        cosClient.shutdown();
        //富文本上传成功后 生成url后自定义一个参数 比如：cosKey= 上传文件key,再调用resetImgPath方法便可以刷新img标签中的url
        //https://abcd?sign=sign&cosKey=dev/2020/07/291596016715726test001.jpg
        //cosKey 非必要，按需添加
        return url.toString() + "&cosKey=" + key;
    }

    /**
     * 重置html中img标签的url地址
     *
     * @param htmlPath 一段带html的文本
     * @throws Exception
     */
    public String resetImgPath(String htmlPath) {
        //富文本上传成功后 生成url后自定义一个参数 比如：cosKey= 上传文件key
        Element body = Jsoup.parse(htmlPath).body();
        Elements elements = body.select("img[src]");
        for (Element element : elements) {
            String src = element.attr("src");
            //重新生成访问地址
            if (StringUtils.isNotBlank(src) && src.contains("&")) {
                int index = src.lastIndexOf("&") + 1;
                String key = src.substring(index);
                if (key.startsWith("cosKey")) {
                    String imgPath = privatedSign(key.split("=")[1]);
                    element.attr("src", imgPath);
                }
            }
        }
        return body.toString();
    }


}
